# -*- coding: utf-8 -*-
#
# Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community Edition) available.
# Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. All rights reserved.
# Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://opensource.org/licenses/MIT
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
#
import dpath
import yaml
import yaml.reader
import re
import tempfile

from urllib.parse import urlparse
from ruamel.yaml import YAML
from ruamel.yaml.compat import StringIO
from ruamel.yaml.compat import ordereddict
from django.utils.translation import ugettext as _

from backend.bcs_k8s.helm.utils.util import fix_rancher_value_by_type, EmptyVaue
from backend.utils.client import make_dashboard_ctl_client
from backend.bcs_k8s.diff import parser

from backend.bcs_k8s.dashboard.exceptions import DashboardExecutionError


yaml.reader.Reader.NON_PRINTABLE = re.compile(
    '[^\x09\x0A\x0D\x20-\x7E\x85\xA0-\uD7FF\uE000-\uFFFD\U00010000-\U0010FFFF]')


def represent_none(self, _):
    return self.represent_scalar('tag:yaml.org,2002:null', '')


yaml.add_representer(type(None), represent_none)


def ruamel_yaml_load(content):
    # be carefule, ruamel.yaml doesn't work well with dpath
    yaml = YAML()
    return yaml.load(content)


def ruamel_yaml_dump(yaml_obj):
    # be carefule, ruamel.yaml doesn't work well with dpath
    yaml = YAML()
    stream = StringIO()
    yaml.dump(yaml_obj, stream=stream)
    content = stream.getvalue()
    return content


def yaml_load(content):
    return yaml.load(content)


def yaml_dump(obj):
    noalias_dumper = yaml.dumper.SafeDumper
    noalias_dumper.ignore_aliases = lambda self, data: True
    return yaml.dump(obj, default_flow_style=False, Dumper=noalias_dumper)


def sync_dict2yaml(obj_list, yaml_content):
    """
    根据 obj_list 的内容更新 yaml_content
    note: 使用 ruamel.yaml 可以保证 yaml_content 可以在load与dump之后还保持注释内容不丢失
    example
    parameters: obj_list
    [
        {
            "name": "aa",
            "type": "int",
            "value": "1"
        },
        {
            "name": "b.c.e",
            "type": "str",
            "value": "3"
        },
        {
            "name": "dd",
            "type": "int",
            "value": 0
        }
    ]
    parameters: yaml_content
    aa: 2
    xx: 1
    b:
      c:
        e: 4
    result:
    aa: 1
    xx: 1
    dd: 0
    b:
      c:
        e: "3"
    """
    yaml_obj = ruamel_yaml_load(yaml_content)
    for item in obj_list:
        try:
            value = fix_rancher_value_by_type(item["value"], item["type"])
        except EmptyVaue:
            continue
        else:
            update = dict()
            dpath.util.new(update, item["name"], value, separator=".")
            dpath.util.merge(
                dst=yaml_obj,
                src=update,
                separator=".",
                flags=dpath.util.MERGE_REPLACE | dpath.util.MERGE_ADDITIVE)

    content = ruamel_yaml_dump(yaml_obj)
    return content


def sync_yaml2dict(obj_list, yaml_content):
    """
    根据 yaml_content 的内容更新 obj_list
    parameters: obj_list
    [
        {
            "name": "aa",
            "type": "int",
            "value": "1"
        },
        {
            "name": "b.c.e",
            "type": "str",
            "value": "3"
        },
        {
            "name": "dd",
            "type": "int",
            "value": 0
        }
    ]
    parameters: yaml_content
    aa: 2
    xx: 1
    b:
      c:
        e: 4
    result:
    [
        {
            "name": "aa",
            "type": "int",
            "value": 2
        },
        {
            "name": "b.c.e",
            "type": "str",
            "value": "4"
        },
        {
            "name": "dd",
            "type": "int",
            "value": 0
        }
    ]
    """
    yaml_obj = ruamel_yaml_load(yaml_content)
    for idx, item in enumerate(obj_list):
        try:
            value = dpath.util.get(yaml_obj, item["name"], separator=".")
        except KeyError:
            pass
        else:
            try:
                value = fix_rancher_value_by_type(value, item["type"])
            except EmptyVaue:
                continue
            else:
                obj_list[idx]["value"] = value

    return obj_list


def safe_get(data, key, default):
    try:
        return int(dpath.util.get(data, key, separator="."))
    except (KeyError, ValueError):
        return default


def collect_resource_state(kube_client, namespace, content):
    state_keys = ["replicas", "readyReplicas", "availableReplicas", "updatedReplicas"]

    def extract_state_info(data):
        return {key: safe_get(data, "status.%s" % key, 0) for key in state_keys}

    with tempfile.NamedTemporaryFile("w") as f:
        f.write(content)
        f.flush()

        res = kube_client.get_by_file(
            filename=f.name,
            namespace=namespace
        )

    result = {
        "summary": {},
        "items": []
    }
    for item in res["items"]:
        state = extract_state_info(item)
        result["items"].append(state)

    for key in state_keys:
        result["summary"][key] = sum([x[key] for x in result["items"]])

    return result


def merge_valuefile(source, new):
    source = ruamel_yaml_load(source)
    if not source:
        source = ordereddict()

    new = ruamel_yaml_load(new)
    dpath.util.merge(source, new)
    return ruamel_yaml_dump(source)


def dashboard_get_overview(kubeconfig, namespace):
    dashboard_client = make_dashboard_ctl_client(
        kubeconfig=kubeconfig
    )
    dashboard_overview = dashboard_client.overview(
        namespace=namespace,
        parameters=dict()
    )
    return dashboard_overview


def extract_state_info_from_dashboard_overview(overview_status, kind, namespace, name):
    for key in overview_status.keys():
        if key.lower() != "{kind}list".format(kind=kind).lower():
            continue

        for k in overview_status[key].keys():
            if k.lower() == "{kind}s".format(kind=kind).lower():
                obj_list = overview_status[key][k]
                break
        else:
            if "items" in overview_status[key]:
                obj_list = overview_status[key]["items"]

        for item in obj_list:
            if item["objectMeta"]["name"].lower() == name.lower():
                return item

    return dict()


def collect_resource_status(base_url, kubeconfig, app, project_code):
    """
    dashboard_client = make_dashboard_ctl_client(
        kubeconfig=kubeconfig
    )
    """

    def status_sumary(status, app):
        if not status and not app.transitioning_result:
            return {
                "messages": _("未找到资源，可能未部署成功，请在Helm Release列表也查看失败原因."),
                "is_normal": False,
                "desired_pods": "-",
                "ready_pods": "-",
            }

        # 暂未实现该类资源状态信息
        if "pods" not in status:
            return {
                "messages": "",
                "is_normal": True,
                "desired_pods": "-",
                "ready_pods": "-",
            }

        messages = [item["message"] for item in status["pods"]["warnings"]]
        messages = filter(lambda x: x, messages)

        desired_pods = safe_get(status, "pods.desired", None)
        ready_pods = safe_get(status, "pods.running", None)
        data = {
            "desired_pods": str(desired_pods),
            "ready_pods": str(ready_pods),
            "messages": "\n".join(messages),
            "is_normal": desired_pods == ready_pods,
        }
        return data

    namespace = app.namespace
    content = app.release.content
    resources = parser.parse(content, app.namespace)
    resources = resources.values()
    release_name = app.name

    dashboard_overview = dashboard_get_overview(
        kubeconfig=kubeconfig,
        namespace=namespace
    )

    result = {}
    structure = app.release.extract_structure(namespace)
    for item in structure:
        kind = item["kind"]
        name = item["name"]

        status = extract_state_info_from_dashboard_overview(
            overview_status=dashboard_overview,
            kind=kind,
            namespace=namespace,
            name=name
        )
        """
        status = {}
        if kind.lower() in ["deployment", "replicaset", "daemonset",
                            "job", "statefulset", "cronjob", "replicationcontroller"]:
            try:
                status = dashboard_client.workload_status(
                    kind=kind,
                    name=name,
                    namespace=namespace,
                    parameters=dict()
                )
            except DashboardExecutionError as e:
                if "handler returned wrong status code: got 404 want 200" in e.output:
                    pass
                else:
                    raise
        """

        if status:
            link = resource_link(
                base_url=base_url,
                kind=kind,
                project_code=project_code,
                name=name,
                namespace=namespace,
                release_name=release_name
            )
        else:
            link = None

        key = "{kind}/{namespace}/{name}".format(
            name=name,
            namespace=namespace,
            kind=kind,
        )
        result[key] = {
            "namespace": namespace,
            "name": name,
            "kind": kind,
            "status": status,
            "status_sumary": status_sumary(status, app),
            "link": link
        }
    return result


def get_base_url(request):
    base_url = request.META.get("HTTP_REFERER") or request.META.get("HTTP_HOST")
    base_url = base_url.split("/console/bcs")[0]
    return base_url


def resource_link(base_url, kind, project_code, name, namespace, release_name):
    kind_map = {
        "deployment": "deployments",
        "statefulset": "statefulset",
        "daemonset": "daemonset",
        "job": "job",
    }
    kind = kind.lower()
    if kind not in kind_map:
        return None

    fix_kind = kind_map[kind]
    url = ("/console/bcs/{project_code}/app/{fix_kind}/{resource_name}/{namespace}/{kind}"
           "?name={resource_name}&namespace={namespace}&category={kind}").format(
               base_url=base_url,
               kind=kind.lower(),
               fix_kind=fix_kind,
               instance_name=release_name,
               resource_name=name,
               project_code=project_code,
               namespace=namespace)
    return url


def compose_url_with_scheme(url, scheme="http"):
    """组装URL
    """
    url_split_info = url.split('//')
    return '{scheme}://{domain}'.format(scheme=scheme, domain=url_split_info[-1])
